import 'dart:io';
import 'package:dio/dio.dart';

class HttpUtil {
  static final HttpUtil _singleton = HttpUtil._internal();

  static HttpUtil get instance => HttpUtil();

  factory HttpUtil() {
    return _singleton;
  }

  static Dio _dio;

  Dio getDio() {
    return _dio;
  }

  HttpUtil._internal() {
    var options = BaseOptions(
      connectTimeout: 15000,
      receiveTimeout: 15000,
      responseType: ResponseType.json,
    );
    _dio = Dio(options);
  }

  Future<dynamic> fetchJson(Uri url, HttpOptions options) async {
    try {
      Response<dynamic> response = await _dio.requestUri(
        url,
        data: options.body,
        options: Options(
          headers: options.headers,
          contentType: ContentType.json,
          responseType: ResponseType.json,
          method: options.method,
        ),
      );

      return response.data;
    } on DioError catch (ex) {
      throw _handleError(ex);
    }
  }

  downloadFile(
    Uri url,
    String savePath,
    HttpOptions options, {
    ProgressCallback onReceiveProgress,
  }) async {
    try {
      var response = await _dio.downloadUri(
        url,
        savePath,
        data: options.body,
        onReceiveProgress: onReceiveProgress,
        options: Options(
          method: options.method,
          headers: options.headers,
        ),
      );
      return response.data;
    } on DioError catch (ex) {
      throw _handleError(ex);
    }
  }

  uploadFile(
    Uri url,
    UploadFileInfo fileInfo,
    HttpOptions options, {
    ProgressCallback onSendProgress,
  }) async {
    try {
      FormData formData = new FormData.from(options.body.toJson());
      formData.add('file', fileInfo);
      var response = await _dio.postUri(
        url,
        data: formData,
        options: Options(
          headers: options.headers,
        ),
        onSendProgress: onSendProgress,
      );
      return response.data;
    } on DioError catch (ex) {
      throw _handleError(ex);
    }
  }

  HttpError _handleError(DioError ex) {
    var response = ex.response;
    var data = ex.response.data is Map
        ? ex.response.data
        : {
            'message': '服务器发生未知错误',
          };

    return HttpError(
      data['message'],
      statusCode: response.statusCode,
      data: data,
    );
  }
}

class HttpOptions {
  String method;
  Map<String, String> headers;
  dynamic body;

  HttpOptions({this.method, this.headers, this.body});
}

class HttpError implements IOException {
  final int statusCode;
  final String message;
  final Map<String, dynamic> data;

  const HttpError(this.message, {this.statusCode, this.data});
}
